package de.kaytastrophe.properties;

/**
 *
 * Generate a Class contains all properties from File.
 * 
 * Sample input:
 * test.properties
 * 
 * #group:test
 * test.a=a
 * test.b=b
 * test.c=c
 * #group:test.bla
 * test.bla.a=blaA
 * test.bla.b=blaB
 * test.bla.c=blaC
 * 
 * Sample output:
 * Test.java
 * 
 * package de.kaytastrophe;
 *
 * public final class Test {
 * 	public static class TestBla {
 * 		public static final String TEST_BLA_A = "blaA";
 * 		public static final String TEST_BLA_B = "blaB";
 * 		public static final String TEST_BLA_C = "blaC";
 * 	}
 *
 *	public static class Test {
 *		public static final String TEST_A = "a";
 *		public static final String TEST_B = "b";
 *		public static final String TEST_C = "c";
 *	}
 *}
 * 
 * 
 * 
 * @author Kay Landeck
 * 
 * 
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

/**
 * Goal which touches a timestamp file.
 * 
 * @goal generate
 * 
 * @phase process-sources
 */
public class PropertyFileGeneratorMojo extends AbstractMojo {
	/**
	 * Location of the file.
	 * 
	 * @parameter expression="${project.build.directory}"
	 * @required
	 */
	private File outputDirectory;

	/**
	 * Location of the file.
	 * 
	 * @parameter
	 * @required
	 */
	private String packageName;

	/**
	 * Location of the file.
	 * 
	 * @parameter
	 * @required
	 */
	private List<String> files;

	public void execute() throws MojoExecutionException {

		getLog().info("outputDir " + outputDirectory);
		getLog().info("package " + packageName);

		final File packageDir = generatePackage();

		for (final String file : files) {

			Set<Group> groups = generateGroups(file);

			getLog().info("run property file generation for file " + file);

			final String className = createClassName(file);

			getLog().info("create Class " + className);

			final File classFile = generateClassFile(packageDir, className);

			FileWriter w = null;
			try {

				w = new FileWriter(classFile);
				w.write("package " + packageName + ";");
				w.write("\npublic final class " + className + "{");
				for (Group group : groups) {
					group.toSource(w);
				}
				w.write("\n}");

			} catch (IOException e) {
				throw new MojoExecutionException("Error creating file ", e);
			} finally {
				if (w != null) {
					try {
						w.close();
					} catch (IOException e) {
						// ignore
					}
				}
			}

		}

	}

	private Set<Group> generateGroups(String file) {

		SortedSet<Group> groups = new TreeSet<>(new Comparator<Group>() {

			@Override
			public int compare(Group g1, Group g2) {
				int result = g2.propertyGroup.length() - g1.propertyGroup.length();
				if(result == 0){
					result = g2.propertyGroup.compareTo(g1.propertyGroup);
				}

				return result;

			}
		});
		BufferedReader r = null;
		try {

			r = new BufferedReader(new FileReader(file));

			String line = null;
			while ((line = r.readLine()) != null) {
				if (line.startsWith("#group:")) {
					groups.add(new Group(line.substring(7)));
				} else if (line.isEmpty() || line.startsWith("#")) {
					// ignore
				} else {
					Group group = groupForProperty(groups, line);
					if (group == null) {
						throw new IllegalStateException(
								"No group found for property " + line);
					} else {
						group.properties.add(line);
					}
				}

			}
		} catch (IOException e) {
			getLog().error(e.getMessage(), e);
		} finally {
			if (r != null) {
				try {
					r.close();
				} catch (IOException e) {
					// ignore
				}
			}
		}
		return groups;
	}

	private File generateClassFile(File packageDir, String className) {
		return new File(packageDir, className + ".java");
	}

	private File generatePackage() {
		String packagePath = packageName.replaceAll("\\.", "/");
		File packageDir = new File(outputDirectory, packagePath);
		if (!packageDir.exists()) {
			packageDir.mkdirs();
		}
		getLog().info("package directory " + packageDir);
		return packageDir;

	}

	private String createClassName(String file) {
		String className = file.substring(file.lastIndexOf('/') + 1,
				file.lastIndexOf('.'));
		className = firstUppercase(className);
		return className;
	}

	private String firstUppercase(String className) {
		className = className.substring(0, 1).toUpperCase()
				+ className.substring(1);
		return className;
	}

	private Group groupForProperty(SortedSet<Group> groups, String line) {
		for (Group g : groups) {
			if (line.startsWith(g.propertyGroup)) {
				return g;
			}
		}
		return null;
	}

	private class Group {

		private final String propertyGroup;
		private final List<String> properties = new ArrayList<>();

		Group(String propertyGroup) {
			this.propertyGroup = propertyGroup;
		}

		public void toSource(FileWriter w) throws IOException {
			w.write("\n\tpublic static class " + className(propertyGroup) + "{");
			for (String prop : properties) {
				int eqIndex = prop.indexOf("=");
				String propName = prop.substring(0, eqIndex);

				w.write("\n\t\tpublic static final String "
						+ constName(propName) + "=" + "\"" + propName + "\";");
			}

			w.write("\n\t}");

		}

		private String className(String groupName) {
			String result = groupName;
			while (result.contains(".")) {
				int dotIdx = result.indexOf(".");
				result = result.substring(0, dotIdx)
						+ firstUppercase(result.substring(dotIdx + 1));
			}
			return firstUppercase(result);
		}

		public String constName(String prop) {
			
			return prop.substring(propertyGroup.length()+1).toUpperCase().replaceAll("\\.", "_");
		}

		@Override
		public String toString() {
			return "Group [propertyGroup=" + propertyGroup + ", properties="
					+ properties + "]";
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + propertyGroup.hashCode();
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Group other = (Group) obj;
			if (!propertyGroup.equals(other.propertyGroup))
				return false;
			return true;
		}

	}

}

